package com.onesports.intelligent.k12.polarlight.config;


import cn.binarywang.wx.miniapp.api.WxMaService;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.onesports.intelligent.k12.polarlight.cache.RedisTokenProvider;
import com.onesports.intelligent.k12.polarlight.common.utils.Result;
import com.onesports.intelligent.k12.polarlight.security.*;
import com.onesports.intelligent.k12.polarlight.security.jwt.JwtTokenProvider;
import com.onesports.intelligent.k12.polarlight.security.wx.WxAuthenticationFilterConfigurer;
import com.onesports.intelligent.k12.polarlight.security.wx.WxAuthenticationProvider;
import io.github.jhipster.config.JHipsterProperties;
import org.apache.http.HttpHeaders;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.CorsFilter;
import org.zalando.problem.spring.web.advice.security.SecurityProblemSupport;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @author aj
 */
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
@Import(SecurityProblemSupport.class)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    private final JHipsterProperties jHipsterProperties;

    private final JwtTokenProvider jwtTokenProvider;
    private final CorsFilter corsFilter;
    private final SecurityProblemSupport problemSupport;
    private final CustomAccessDecisionManager accessDecisionManager;
    private final CustomSecurityMetadataSource securityMetadataSource;
    private final RedisTokenProvider redisTokenProvider;
    private final DomainUserDetailsService domainUserDetailsService;
    private final WxMaService wxMaService;

    public SecurityConfiguration(
            JwtTokenProvider jwtTokenProvider,
            CorsFilter corsFilter,
            JHipsterProperties jHipsterProperties,
            SecurityProblemSupport problemSupport,
            CustomAccessDecisionManager accessDecisionManager,
            CustomSecurityMetadataSource securityMetadataSource,
            RedisTokenProvider redisTokenProvider,
            DomainUserDetailsService domainUserDetailsService,
            WxMaService wxService) {
        this.jwtTokenProvider = jwtTokenProvider;
        this.corsFilter = corsFilter;
        this.problemSupport = problemSupport;
        this.jHipsterProperties = jHipsterProperties;
        this.accessDecisionManager = accessDecisionManager;
        this.securityMetadataSource = securityMetadataSource;
        this.redisTokenProvider = redisTokenProvider;
        this.domainUserDetailsService = domainUserDetailsService;
        this.wxMaService = wxService;
    }

    @Bean
    public DaoAuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setHideUserNotFoundExceptions(false);
        provider.setUserDetailsService(domainUserDetailsService);
        provider.setPasswordEncoder(new BCryptPasswordEncoder());
        return provider;
    }


    @Bean
    public JsonAuthenticationFilter authenticationFilter() throws Exception {
        JsonAuthenticationFilter filter = new JsonAuthenticationFilter();
        filter.setAuthenticationManager(authenticationManagerBean());
        filter.setAuthenticationSuccessHandler(new LoginSuccessHandler(redisTokenProvider, jwtTokenProvider));
        filter.setAuthenticationFailureHandler(new LoginFailHandler());
        filter.setFilterProcessesUrl("/auth/login");
        filter.setPostOnly(true);
        return filter;
    }

    @Bean
    public WxAuthenticationProvider wxAuthenticationProvider() {
        return new WxAuthenticationProvider();
    }

    @Override
    public void configure(WebSecurity web) {
        web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**")
                .antMatchers("/swagger-ui/**")
                .antMatchers("/test/**")
                .antMatchers("/i18n/**")
                .antMatchers("/doc.html")
                .antMatchers("/swagger-resources/**")
                .antMatchers("/api-docs*")
                .antMatchers("/webjars/**")
                .antMatchers("/v2/api-docs")
                .antMatchers("/druid/**")
                .antMatchers("/captcha/**");
    }

    @Override
    protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
//        auth.inMemoryAuthentication()
//            .withUser("admin").password(passwordEncoder().encode("admin")).roles("ADMIN");
//        auth.userDetailsService(domainUserDetailsService).passwordEncoder(new BCryptPasswordEncoder());
        auth.authenticationProvider(authenticationProvider());
    }


    @Override
    public void configure(HttpSecurity http) throws Exception {
        // @formatter:off
        http
                .logout().logoutUrl("/auth/logout")
                .logoutSuccessHandler(logoutSuccessHandler())
                .and()
                .exceptionHandling()
                .authenticationEntryPoint(problemSupport)
                .accessDeniedHandler(problemSupport)
                .and()
                .csrf()
                .disable()
                .apply(wxConfigurerAdapter())
                .and()
                .addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class)
                .addFilterBefore(authenticationFilter(), UsernamePasswordAuthenticationFilter.class)
                .headers()
                .referrerPolicy(ReferrerPolicyHeaderWriter.ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN)
                .and()
                .featurePolicy("geolocation 'none'; midi 'none'; sync-xhr 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope 'none'; fullscreen 'self'; payment 'none'")
                .and()
                .frameOptions()
                .deny()
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                .antMatchers("/websocket/**").authenticated()
                .antMatchers("/management/health").permitAll()
                .antMatchers("/management/health/**").permitAll()
                .antMatchers("/management/info").permitAll()
                .antMatchers("/management/prometheus").permitAll()
                .antMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN)
                .withObjectPostProcessor(new FilterSecurityInterceptorPostProcessor(accessDecisionManager, securityMetadataSource))
                .and()
                .apply(securityConfigurerAdapter());
        ;
        // @formatter:on
    }

    private TokenAuthenticationConfigurer securityConfigurerAdapter() {
        return new TokenAuthenticationConfigurer(jwtTokenProvider, redisTokenProvider,problemSupport);
    }

    private WxAuthenticationFilterConfigurer wxConfigurerAdapter() {
        return new WxAuthenticationFilterConfigurer(wxAuthenticationProvider(), wxMaService, jwtTokenProvider, redisTokenProvider);
    }


    private LogoutSuccessHandler logoutSuccessHandler() { //登出处理
        return (httpServletRequest, httpServletResponse, authentication) -> {
            redisTokenProvider.removeToken(resolveToken(httpServletRequest));
            Result result = new Result();
            result.setCode(Result.SUCCESS);
            ObjectMapper mapper = new ObjectMapper();
            httpServletResponse.setCharacterEncoding("UTF-8");
            httpServletResponse.setContentType("application/json;charset=UTF-8");
            httpServletResponse.setStatus(HttpServletResponse.SC_OK);
            httpServletResponse.getWriter().append(mapper.writeValueAsString(result));
        };
    }

    private String resolveToken(HttpServletRequest request) {
        String bearerToken = request.getHeader(HttpHeaders.AUTHORIZATION);
        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7);
        }

        return null;
    }
}
